﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace ChengNet.StrongCaml.Node
{
    /// <summary>
    /// Abstract CAML tag representation
    /// </summary>
    public abstract class CamlNode
    {
        /// <summary>
        /// Name/Value pairs for a CAML tag
        /// </summary>
        protected IDictionary<string, string> Attributes;
        /// <summary>
        /// A CAML tag's children tags
        /// </summary>
        protected IList<CamlNode> Children;
        /// <summary>
        /// Stores result of <see cref="ToString"/>
        /// </summary>
        protected string serialized;

        /// <summary>
        /// Catch-all constructor for adding children nodes of any type
        /// </summary>
        /// <param name="childNodes"></param>
        protected CamlNode(params CamlNode[] childNodes) { Children = childNodes.Where(n => n != null).ToList(); }
        
        /// <summary>Implicity wraps raw data with a <see cref="Node.Literal"/></summary>
        public static implicit operator CamlNode(string str) { return new Literal(str); }
        /// <summary>Implicity wraps raw data with a <see cref="Node.Literal"/></summary>
        public static implicit operator CamlNode(int value) { return new Literal(value); }
        /// <summary>Implicity wraps raw data with a <see cref="Node.Literal"/></summary>
        public static implicit operator CamlNode(uint value) { return new Literal(value); }
        /// <summary>Implicity wraps raw data with a <see cref="Node.Literal"/></summary>
        public static implicit operator CamlNode(bool value) { return new Literal(value); }
        /// <summary>Implicity wraps raw data with a <see cref="Node.Literal"/></summary>
        public static implicit operator CamlNode(DateTime value) { return new Literal(value.ToString("yyyy-MM-ddTHH:mm:ssZ")); }

        /// <summary>
        /// Recursively serializes tag and its children into a string
        /// </summary>
        /// <returns></returns>
        public override string ToString() {
            if (!string.IsNullOrEmpty(serialized))
                return serialized;

            var tagName = this.GetType().Name;

            if (tagName.Contains('`'))
                tagName = tagName.Remove(tagName.IndexOf('`'));

            var childrenSerialized = Children.Where(n => n != null).Select(n => n.ToString());
            var innerSerialized = string.Join(string.Empty, childrenSerialized.ToArray());

            var isEmpty = Children.Count == 0;
            var hasAttribs = Attributes != null;

            if (!hasAttribs)
                serialized = isEmpty ?
                    string.Format("<{0} />", tagName) :
                    string.Format("<{0}>{1}</{0}>", tagName, innerSerialized);
            else {
                var attribsSerialized = string.Join(" ", Attributes.Select(a => string.Format("{0}=\"{1}\"", a.Key, a.Value)).ToArray());
                serialized = isEmpty ?
                    string.Format("<{0} {1} />", tagName, attribsSerialized) :
                    string.Format("<{0} {1}>{2}</{0}>", tagName, attribsSerialized, innerSerialized);
            }
            return serialized;
        }

        /// <summary>
        /// Implicitly serializes a <see cref="CamlNode"/>.
        /// </summary>
        public static implicit operator string(CamlNode camlNode) { return camlNode.ToString(); }
    }
}
